---
title: Accumulate and render user messages
summary: Build a single de-duplicated, channel-appropriate message string for the caller.
status: draft
owner: SAIF Documentation
last_reviewed: 2026-08-26
library: SAIF.Application
reference_type: embedded
operation_id: accumulate-and-render-user-messages
trigger: A derived manager reports a validation failure, save result, or informational note
---

# Accumulate and render user messages

Accumulates message text onto a manager instance, skipping duplicates and blank input, and renders the
accumulated messages for the caller's target channel (web, Windows, or a caller-defined separator).

| Field | Value |
| --- | --- |
| **Operation ID** | accumulate-and-render-user-messages |
| **Trigger** | A derived manager reports a validation failure, save result, or informational note |
| **Inputs** | One message string, a `String()`, or an `IEnumerable(Of String)` |
| **Outputs** | The `Message` property (single concatenated string) |
| **Primary components** | `AppManagerBase.AppendMessageLines`, `MessageFormatting` |
| **Entry point** | `SAIF.Application/AppManagerBase.vb`, `SAIF.Application/MessageFormatting.vb` |

## Steps

1. `AppendMessageLines(IEnumerable(Of String))` converts to an array and delegates to the array
   overload, which loops to the single-string overload.
2. The single-string overload skips zero-length input and any text already contained in `Message`
   ([BR-APP-005](../business-rules.md#br-app-005-duplicate-message-text-is-never-shown-twice)); when
   `Message` is already non-empty, `NewLineCharacter` (default `<BR>`) is appended before the new text.
3. For batch rendering, callers use `MessageFormatting.GetMessagesForWeb` /
   `GetMessagesForWindows` / `GetMessagesUserDefined`: each strips pre-existing `vbCrLf` or `<BR>`
   markers, drops duplicates, then joins with the chosen separator, omitting the trailing separator
   after the final element
   ([BR-APP-010](../business-rules.md#br-app-010-messages-render-with-a-channel-appropriate-defaulted-separator)).
4. `AppendMessageString` appends the joined result to an existing `ByRef` buffer, inserting a literal
   `<br>` first when that buffer is non-blank — this path ignores the caller's separator for the seam.*

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start(["AppendMessageLines<br>called"]) --> Loop["Loop to single-string<br>overload"]
	Loop --> Skip{"Blank or<br>already present?"}
	Skip -->|Yes| Discard["Skip text"]
	Skip -->|No| Prefix{"Message<br>non-empty?"}
	Prefix -->|Yes| AddSep["Append<br>NewLineCharacter"]
	Prefix -->|No| Append["Append text"]
	AddSep --> Append
	Append --> Render["MessageFormatting renders<br>for target channel"]
```

## Failure and edge-case behavior
- `AppendMessageString` ignores the caller's chosen separator when bridging to an existing `ByRef`
  buffer, always inserting a literal `<br>` — an inconsistency with the channel-appropriate separator
  rule applied elsewhere.*

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
